package net.aiion.weave.impl.bytecode;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import net.aiion.weave.asm.ClassReader;
import net.aiion.weave.impl.bytecode.asm.BytecodeASMAction;
import net.aiion.weave.impl.bytecode.asm.BytecodeASMClassVisitor;
import net.aiion.weave.spi.bytecode.Bytecode;
import net.aiion.weave.spi.bytecode.BytecodeEventFactory;
import net.aiion.weave.spi.bytecode.BytecodeException;
import net.aiion.weave.spi.bytecode.BytecodeFinder;
import net.aiion.weave.spi.bytecode.BytecodeMatcher;
import net.aiion.weave.spi.bytecode.events.BytecodeEvent;
import net.aiion.weave.spi.resources.Resource;

public class BytecodeFinderImpl implements BytecodeFinder {
    private final Resource resource;
    private final Collection<BytecodeMatcher> bytecodeMatchers;
    private final Map<BytecodeMatcher, Boolean> bytecodeMatcherResults;

    private ClassReader classReader;
    private BytecodeASMClassVisitor classVisitor;

    private boolean finished;

    public BytecodeFinderImpl(final Resource aResource) {
        resource = aResource;
        bytecodeMatchers = new ArrayList<>();
        bytecodeMatcherResults = new HashMap<>();
    }

    @Override
    public BytecodeFinder addMatcher(final BytecodeMatcher aBytecodeMatcher) {
        bytecodeMatchers.add(aBytecodeMatcher);
        return this;
    }

    @Override
    public boolean hasAny() {
        startSourcing();
        return !bytecodeMatcherResults.isEmpty();
    }

    @Override
    public boolean hasAll() {
        startSourcing();
        return bytecodeMatcherResults.size() == bytecodeMatchers.size();
    }

    private void startSourcing() {
        if (finished) {
            return;
        }

        try (InputStream is = resource.openStream()) {
            classVisitor = new BytecodeASMClassVisitor(Bytecode.getEventFactory(), new DispatchBytecodeAction());
            classReader = new ClassReader(is);
            classReader.accept(classVisitor, 0);
        } catch (final IOException e) {
            throw new BytecodeException(e);
        } finally {
            classReader = null;
            classVisitor = null;
            finished = true;
        }
    }

    private class DispatchBytecodeAction implements BytecodeASMAction {
        public DispatchBytecodeAction() {
        }

        @Override
        public void processBytecode(final BytecodeEvent aBytecodeEvent) {
            for (BytecodeMatcher bytecodeMatcher : bytecodeMatchers) {
                if (bytecodeMatcher.matchesBytecodeEvent(aBytecodeEvent)) {
                    bytecodeMatcherResults.put(bytecodeMatcher, true);
                }
            }
        }
    }
}
